<template>
	<div class="container" :class="{ disabled: disabled }">
		<!-- 上传按钮 -->
		<div id="picker">
			<div class="shadow"></div>
			<slot name="default"></slot>
		</div>
		<upload-list
			v-if="fileArr.length || echoFileArr.length"
			:file-arr="fileArr"
			:echo-file-arr="echoFileArr"
			:percentage="percentage"
			:disabled="disabled"
			:engine-percentages="enginePercentages"
			@delete-upload="deleteUpload"></upload-list>
	</div>
</template>

<script lang="ts" setup>
import { ref, watch, onMounted, onUnmounted } from 'vue';
import { useDebounceFn } from '@vueuse/core';
import { ElMessage } from 'element-plus';
import md5 from 'js-md5';
import { uuid } from '@CIF/Utils/util';
import { getToken } from '@/utils/auth';
import { usePrimaryStore } from '@/store';
import {
	getUploadInfo,
	getFileBlockList,
	getUploadId,
	// deleteFileInfo,
	mergeFile,
	getGroupFileInfo
} from './api.js';
import UploadList from './upload-list/index.vue';

const props = defineProps({
	// 文件组标识
	value: {
		type: String,
		default: ''
	},
	autoUpload: {
		type: Boolean,
		default: false
	},
	// 允许上传的文件类型
	accept: {
		type: Array,
		default: null
	},
	// 文件上传的最大数量
	fileNumLimit: {
		type: Number,
		default: 10
	},
	// 文件大小限制
	fileSingleSizeLimit: {
		type: Number,
		default: null
	},
	// 传递给后端的参数
	info: {
		type: Object,
		default() {
			return {};
		}
	},
	// 是否允许上传时选择多个文件
	isMultiple: {
		type: Boolean,
		default: false
	},
	// 文件分片大小
	fileBlockSize: {
		type: Number,
		// default: 1024, // 1k
		default: 100 * 1024 * 1024 // 100M
	},
	// 是否禁用
	disabled: {
		type: Boolean,
		default: false
	},
	// 文件上传引擎进度
	enginePercentages: {
		type: Array,
		default() {
			return [];
		}
	}
});

const emit = defineEmits([
	'input',
	'lineChange',
	'uploadSuccess',
	'uploadCancel',
	'uploadError',
	'fileChange'
]);

const isLine = ref(navigator.onLine); // 当前网络状态(H5新增对象，true表示在线)
const btnId = '#picker'; // 上传按钮id
const uploader = ref<WebUploader.Uploader>({}); // WebUploader对象
const url = '/api/sys-storage/bigFile'; // 文件上传地址
const paramArr = ref<any>({}); // 保存文件参数
const fileBlockListObj = ref<any>({}); // 保存本次已上传的分片文件信息，文件合并时传递给后端
const blockInfoObj = ref<any>({}); // 断点续传时暂存已上传的分片信息
const isPassSecond = ref<any>({}); // 记录文件极速秒传状态
const fileArr = ref<WebUploader.File>([]); // 已选文件数组
const echoFileArr = ref<WebUploader.File>([]); // 回显文件数组
const percentage = ref<any>({}); // 上传进度
const md5Obj = ref<any>({}); // 文件MD5信息
const groupToken = ref('');
const e9y = ref(''); // 指定文件上传的服务器
const notAcceptFileTypes: string[] = []; // 本次选择的文件中不允许上传的文件类型

// 格式化文件类型数组
// const getAccept = computed(() => {
//   const arrs = props.accept;
//   let extensions = '';
//   let mimeTypes = '';
//   arrs.forEach((value) => {
//     extensions += `${value}, `;
//     mimeTypes += `.${value}, `;
//   });
//   return {
//     // 允许的文件格式
//     extensions,
//     mimeTypes,
//   };
// });

// 获取当前网络状态
const getLineStatus = (e: any) => {
	isLine.value = e.type === 'online';
};
// 获取文件key
const getFileKey = (fileObj: WebUploader.File) => {
	return `${fileObj.name}&${fileObj.lastModifiedDate}`;
};
// 获取文件的md5Key
const getMd5Key = (fileObj: WebUploader.File) => {
	// 文件名+文件大小+最后修改时间
	const key = fileObj.name + fileObj.size + fileObj.lastModifiedDate;

	return new Promise((res) => {
		const md5Key = md5(key);
		res(md5Key);
	});
};

// 上传进度条的颜色
// const proColor = computed(() => {
//   return isLine.value ? 'rgb(64, 158, 255)' : 'rgb(144, 147, 153)';
// });

// 重置队列
// const reset = () => {
//   uploader.value.reset();
// };

// 修改暂存的信息
const amendInfo = (fileObj: WebUploader.File, key: string) => {
	console.log('amendInfo', fileObj);

	fileArr.value.some((file: WebUploader.File, index: number) => {
		if (file.id === fileObj.id) {
			fileArr.value.splice(index, 1);
			return true;
		}
		return false;
	});

	delete percentage.value[key];
	delete md5Obj.value[key];
	delete fileBlockListObj.value[key];
	delete blockInfoObj.value[key];
};

const deleteUpload = (fileObj: WebUploader.File) => {
	amendInfo(fileObj, getFileKey(fileObj));
};

/* 文件操作 */
// 暂停文件上传：第一个参数为是否中断当前正在上传的文件，若第一个参数是文件，则只暂停指定文件
const stop = () => {
	uploader.value.stop(); // 暂停队列中所有的文件
	// this.uploader.stop(fileObj, true) // 暂停指定文件：个人认为源码存在bug，无法正常使用，只能对单个分片的文件生效
};

// 重试上传：重试指定文件，或者从出错的文件开始重新上传。
const retry = () => {
	uploader.value.retry();
	// this.uploader.retry(fileObj)
};

// 监听网络状态变化
watch(
	() => isLine.value,
	(n) => {
		if (n) {
			emit('lineChange', true);
			retry(); // 重新上传
		} else {
			emit('lineChange', false);
			stop(); // 暂停上传
		}
	}
);

// 上传时选择文件夹中文件数量过多，防抖函数
// 相同文件
const debounceSameFile = useDebounceFn((fileObj: WebUploader.File) => {
	const reason = '不要重复传同一个文件';
	ElMessage.error({
		message: reason
	});
	emit('uploadError', 1, fileObj, reason);
}, 200);

// 超过数量
const debounceNumLimit = useDebounceFn((fileObj: WebUploader.File) => {
	const reason = '上传文件的数量超出限制';
	ElMessage.error({
		message: reason
	});
	emit('uploadError', 1, fileObj, reason);
}, 200);

// 空文件
const debounceEmptyFile = useDebounceFn((fileObj: WebUploader.File) => {
	const reason = '请勿上传空文件';
	ElMessage.error({
		message: reason
	});
	emit('uploadError', 1, fileObj, reason);
}, 200);

// 不允许的文件类型
const debounceCheckFn = useDebounceFn((fileObj: WebUploader.File, reason: string) => {
	ElMessage.error({
		message: reason
	});
	emit('uploadError', 1, fileObj, reason);
	notAcceptFileTypes.length = 0;
}, 200);

// 初始化WebUpload组件，注册监听事件
const initWebUpload = () => {
	uploader.value = WebUploader.create({
		auto: props.autoUpload, // 选择文件后自动上传
		// swf: './static/Uploader.swf', //swf文件路径
		server: url, // 文件上传地址
		pick: {
			id: btnId, // 文件上传的按钮
			multiple: props.isMultiple // 是否允许上传时选择多个文件
			// innerHTML: '上传' //指定按钮文字
		},
		dnd: btnId, // 开启文件拖拽功能，拖拽区域为btnId
		// accept: props.accept ? getAccept : '', // 允许上传的文件格式
		compress: false, // 图片在上传前不进行压缩
		// threads: 1, // 分片上传并发数 默认值：3
		fileNumLimit: props.fileNumLimit, // 限制上传文件总数
		fileSingleSizeLimit: props.fileSingleSizeLimit, // 限制单个上传文件的大小
		chunked: true, // 分片上传
		chunkSize: props.fileBlockSize, // 分片大小 插件默认值：5M
		// chunkRetry: false, //当前分片的最大重传次数 默认值：2
		// prepareNextFile: true, //上传当前文件时预处理下一文件 默认值：false
		duplicate: true, // 重复上传
		formData: { e9y: e9y.value } // 发送请求时携带的参数 默认值：{}
	});

	// 当文件被加入到队列前触发，进行校验
	uploader.value.on('beforeFileQueued', (fileObj: WebUploader.File) => {
		const key = getFileKey(fileObj);
		if (percentage.value[key]) {
			debounceSameFile(fileObj);
			return false;
		}
		if (
			props.fileNumLimit &&
			fileArr.value.length + echoFileArr.value.length >= props.fileNumLimit
		) {
			debounceNumLimit(fileObj);
			return false;
		}
		if (fileObj.size === 0) {
			debounceEmptyFile(fileObj);
			return false;
		}
		// 文件格式校验
		if (!props.accept.includes(fileObj.ext)) {
			if (!notAcceptFileTypes.includes(fileObj.ext)) {
				notAcceptFileTypes.push(fileObj.ext);
			}
			debounceCheckFn(fileObj, `'不允许的文件格式'：${notAcceptFileTypes.join(', ')}`);
			return false;
		}

		fileArr.value.push({ ...fileObj, description: '' });
		percentage.value[key] = 1;
		return true;
	});

	// 当前文件的分片在发送前触发
	uploader.value.on('uploadBeforeSend', (fileObj: WebUploader.File, data: any, headers: any) => {
		console.log('uploadBeforeSend 分片发送前', fileObj);
		// 添加请求头
		headers['Fusion-Auth'] = getToken() || '';

		// data参数即为formData
		const key = getFileKey(fileObj.file);
		data.uploadId = paramArr.value[key].uploadId;
		data.objectName = paramArr.value[key].objectName;
		if (paramArr.value[key].chunk === 0) {
			data.chunk = paramArr.value[key].chunk;
		}
	});

	const store = usePrimaryStore();
	// 上传过程中触发，更新上传进度(progress为文件总进度，非分片进度)
	uploader.value.on('uploadProgress', (fileObj: WebUploader.File, progress: number) => {
		console.log('uploadProgress', fileObj, progress);
		const key = getFileKey(fileObj);
		const num = Math.round(progress * 100);
		if (percentage.value[key] && percentage.value[key] > num) {
			return;
		}
		// if (num === 100) {
		// 	percentage.value[key] = 99;
		// } else
		percentage.value[key] = num;

		// store.setUploadProgress({
		// 	cdePercentage: percentage.value
		// });
	});

	// 当分片文件上传到服务端响应后触发
	uploader.value.on('uploadAccept', (fileBlockObj: WebUploader.File, res: any) => {
		if (!res.status) {
			console.log('uploadAccept', fileBlockObj, res);
			const fileObj = fileBlockObj.file; // 获取当前分片的主文件
			const key = getFileKey(fileObj);
			amendInfo(fileObj, key); // 修改暂存的信息
			emit('uploadError', 2, fileObj, res);
		}
	});

	// 完整文件上传成功时触发：通知后台进行文件合并
	uploader.value.on('uploadSuccess', (fileObj: WebUploader.File, response: any) => {
		console.log('uploadSuccess', fileObj, response);

		const key = getFileKey(fileObj);
		if (isPassSecond.value[key]) {
			// 极速秒传，不需要通知合并
			return;
		}

		// 容错：处理上传进度误差
		if (percentage.value[key] < 100) {
			percentage.value[key] = 100;
		}

		const data = {
			e9y: e9y.value,
			md5: md5Obj.value[key],
			list: fileBlockListObj.value[key],
			uploadId: paramArr.value[key].uploadId,
			objectName: paramArr.value[key].objectName,
			fileName: fileObj.name,
			fileSize: fileObj.size,
			groupToken: groupToken.value,
			info: props.info
		};
		// 合并文件
		mergeFile(data)
			.then((res) => {
				if (res.status) {
					// 设置fileToken
					fileArr.value.some((file: WebUploader.File, index: number) => {
						if (file.id === fileObj.id) {
							fileArr.value[index].fileToken = res.data.fileToken;
							return true;
						}
						return false;
					});
					delete fileBlockListObj.value[key];
					delete blockInfoObj.value[key];
					emit('uploadSuccess', fileObj, res);
				} else {
					amendInfo(fileObj, key);
					emit('uploadError', 3, fileObj, res);
				}
			})
			.catch((error) => {
				emit('uploadError', 3, fileObj, error);
			});
	});

	// 文件上传出错时触发
	uploader.value.on('uploadError', (fileObj: WebUploader.File, reason: any) => {
		// 秒传接口失败后会触发，此时reason为undefined
		if (reason) {
			console.log('uploadError', fileObj, reason);
			// 放过断网引起的error
			if (reason === 'abort') {
				return;
			}

			const key = getFileKey(fileObj);
			amendInfo(fileObj, key); // 修改暂存的信息
			emit('uploadError', 2, fileObj, reason);
		}
	});

	// 内置错误类型
	uploader.value.on('error', (type: string) => {
		console.log('error', type);
		let errorMessage;

		if (type === 'F_EXCEED_SIZE') {
			errorMessage = `'上传文件的大小不能超过'${props.fileSingleSizeLimit / (1024 * 1000)}M`;
		} else if (type === 'Q_EXCEED_NUM_LIMIT') {
			errorMessage = '文件并发上传数量超出限制';
		} else if (type === 'Q_TYPE_DENIED') {
			errorMessage = '文件并发上传数量超出限制';
		} else {
			errorMessage = '文件上传出错，请检查后重新上传';
		}

		const fileObj = fileArr.value.pop(); // 删除已选文件数组中新增的文件

		const key = getFileKey(fileObj);
		delete percentage.value[key];

		emit('uploadError', 1, null, errorMessage);
	});
};

// 初始化文件组信息
const initGroupData = () => {
	if (!props.value) {
		groupToken.value = uuid(16, 32);
		emit('input', groupToken.value);
		return;
	}

	groupToken.value = props.value;
	const data = {
		g9s: new Array(groupToken.value)
	};
	// 获取已上传的文件组信息
	getGroupFileInfo(data).then((res) => {
		if (res.status && res.data) {
			res.data.forEach((file: WebUploader.File) => {
				echoFileArr.value.push(file);
			});
		}
	});
};
initGroupData();

// 手动上传（供父组件调用）
const submit = () => {
	uploader.value.upload();
};

defineExpose({
	submit
});

watch(
	fileArr,
	() => {
		emit('fileChange', fileArr.value);
	},
	{ deep: true }
);

onMounted(() => {
	window.addEventListener('online', getLineStatus); // 网络由异常状态到正常状态
	window.addEventListener('offline', getLineStatus); // 网络由正常状态到异常状态

	// 文件开始发送前触发，WebUploader会等待此异步操作完成后，再上传文件(主要用于处理异步操作)
	WebUploader.Uploader.register({
		name: 'bsf',
		'before-send-file': (fileObj: WebUploader.File) => {
			console.log('before-send-file');
			const deferred = WebUploader.Deferred();
			const key = getFileKey(fileObj);

			getMd5Key(fileObj)
				.then((md5Value) => {
					md5Obj.value[key] = md5Value;

					const data = {
						fileName: fileObj.name,
						fileSize: fileObj.size,
						md5: md5Value,
						g9s: groupToken.value
					};
					// 获取当前文件上传情况（秒传接口）
					getUploadInfo(data)
						.then((res: any) => {
							if (!res.status) {
								// 异常状态：文件类型不符合后端限制等情况 TODO
								emit('uploadError', 4, fileObj, res);
								amendInfo(fileObj, key);
								deferred.reject(); // 注意：会触发webUpload的uploadError
							} else if (res.data.resultType === 0) {
								// 极速秒传
								isPassSecond.value[key] = true;
								percentage.value[key] = 99;
								fileArr.value.some((file: WebUploader.File, index: number) => {
									if (file.id === fileObj.id) {
										fileArr.value[index].fileToken = res.data.sysOssVO.fileToken;
										return true; // 中断循环
									}
									return false;
								});
								setTimeout(() => {
									percentage.value[key] = 100;
									emit('uploadSuccess', fileObj, res.data.sysOssVO.groupToken);
								}, 500);

								uploader.value.skipFile(fileObj); // 跳过当前文件，直接标记为已上传状态
								deferred.resolve();
							} else if (res.data.resultType === 2) {
								// 断点续传
								paramArr.value[key] = {
									objectName: res.data.bigFileVO.objectName,
									uploadId: res.data.bigFileVO.uploadId
								};

								// 完整文件 <= 分片大小时，需要手动添加chunk参数
								if (fileObj.size <= props.fileBlockSize) {
									paramArr.value[key].chunk = 0;
								}

								const params = {
									objectName: res.data.bigFileVO.objectName,
									uploadId: res.data.bigFileVO.uploadId,
									endPointKey: e9y.value
								};

								// 获取当前文件已上传的分片列表
								getFileBlockList(params).then((response: any) => {
									if (response.status) {
										// 暂存已上传分片信息
										if (response.data.length > 0) {
											blockInfoObj.value[key] = {};
											response.data.forEach((item: any) => {
												blockInfoObj.value[key][item.etag] = `${item.size}-${item.partNumber}`;
											});
										}

										deferred.resolve();
									} else {
										emit('uploadError', 4, fileObj, response);
										amendInfo(fileObj, key);
										deferred.reject();
									}
								});
							} else {
								// 普通上传 resultType为1
								const params = {
									fileName: fileObj.name,
									md5: md5Value,
									e9y: e9y.value
								};

								// 获取当前上传文件唯一标识
								getUploadId(params)
									.then((response: any) => {
										if (response.status) {
											paramArr.value[key] = {
												objectName: response.data.objectName,
												uploadId: response.data.uploadId
											};

											// 完整文件 <= 分片大小时，需要手动添加chunk参数
											if (fileObj.size <= props.fileBlockSize) {
												paramArr.value[key].chunk = 0;
											}

											deferred.resolve();
										} else {
											emit('uploadError', 4, fileObj, response);
											amendInfo(fileObj, key);
											deferred.reject();
										}
									})
									.catch((error: any) => {
										emit('uploadError', 4, fileObj, error);
										amendInfo(fileObj, key);
										deferred.reject();
									});
							}
						})
						.catch((error: any) => {
							emit('uploadError', 4, fileObj, error);
							amendInfo(fileObj, key);
							deferred.reject();
						});
				})
				.catch((error: any) => {
					emit('uploadError', 2, fileObj, error);
					amendInfo(fileObj, key);
					deferred.reject();
				});

			return deferred.promise(); // 继续执行分片上传
		}
	});

	WebUploader.Uploader.register({
		name: 'bs',
		// 分片文件在发送前触发：检查当前分片是否已经上传 断点续传
		'before-send': (fileBlock: any) => {
			console.log('before-send', fileBlock);
			const deferred = WebUploader.Deferred();
			const key = getFileKey(fileBlock.file);

			// 计算分片md5值
			uploader.value
				.md5File(fileBlock.blob)
				.then((md5Value: string) => {
					// 保存当前分片信息
					if (!fileBlockListObj.value[key]) {
						fileBlockListObj.value[key] = new Array({
							etag: md5Value,
							number: fileBlock.chunk + 1
						});
					} else {
						fileBlockListObj.value[key].push({
							etag: md5Value,
							number: fileBlock.chunk + 1
						});
					}

					if (blockInfoObj.value[key] && blockInfoObj.value[key][md5Value]) {
						// 断点续传
						deferred.reject(); // 跳过当前分片 注意：不会触发webUpload的uploadError
					} else {
						deferred.resolve();
					}
				})
				.catch((error: any) => {
					emit('uploadError', 2, fileBlock, error);
					amendInfo(fileBlock, key);
				});

			return deferred.promise(); // 继续执行分片上传
		}
	});

	// 初始化webUploader
	initWebUpload();
});

onUnmounted(() => {
	window.removeEventListener('online', getLineStatus);
	window.removeEventListener('offline', getLineStatus);

	WebUploader.Uploader.unRegister('bsf');
	WebUploader.Uploader.unRegister('bs');
});
</script>

<style lang="scss" scoped>
.container {
	position: relative;
	padding-right: 5px;
	.shadow {
		position: absolute;
		width: 100%;
		height: 100%;
		z-index: 1;
		display: none;
	}
}

.disabled {
	.shadow {
		display: block;
		cursor: not-allowed;
	}
}

:deep(.webuploader-container) {
	width: 100%;
	position: relative;
	display: inline-block;
}

:deep(.webuploader-element-invisible) {
	display: none;
}
</style>
